/* eslint-disable no-bitwise */
/**
 * 默认报告样式
 *
 * Created by snowingsea on 2021/01/19.
 */
const fs = require('fs-extra');
const path = require('path');

const files = require('./../../../files');
const functions = require('./../../../functions');
const { mcdcKind, coverageKind } = require('./../../../tests');
const { isTestableElement } = require('./../../../file-elements');
const utility = require('./../../../utility');
const ParentReporter = require('./../default-report/index');
const ProjectVersionSpliter = require('./../../project-version-spliter');
const DataAdapter = require('./data-adapter');
const TargetMachineDataReport = require('./target-machine-data-adapter');
const {
  generateStatemetBlocks,
  generateBranchBlocks,
  generateConditionBlocks,
  generateDecisionBlocks,
  generateMcDcBlocks,
  generateCondMcDcBlocks,
} = require('./../functionUtil');

class DefaultReporter extends ParentReporter {
  constructor(versionId, locale, version = null, project = null, creator = null, outputDir = null, format, complete) {
    super(versionId, locale, version, project, creator, outputDir, format, complete);
    this.setDataAdapter(new DataAdapter());
    this.baseDir = __dirname;
  }

  getReportName() {
    return '软件测试报告';
  }

  isIntegrationTest() {
    return false;
  }

  setTargetMachineDataSource(data) {
    this.setDataAdapter(new TargetMachineDataReport(data.filesMap, data.functionsMap, data.testcasesMap, this.version, this.project));
  }

  async handleFile(file, versionDir) {
    const reportPath = `${path.join(versionDir, path.dirname(file.path), file.fileName)}`;
    const res = await ProjectVersionSpliter.prototype.handleFile.call(this, file, versionDir);
    const { typeSystem } = res;
    let funcs = res.functions;
    if (!funcs.length) {
      return Promise.resolve();
    }
    funcs = funcs.filter((func) => isTestableElement(func.kind));
    const fileContent = await files.getLinesOfRemoteFileStream(file._id, {});
    const linesInSourceFile = fileContent.map(({ content }) => content);
    await utility.arrayChunkOperation(funcs, 10, async (func) => {
      func.functionCfg = await this.getDataAdapter().getFunctionCfg(func);
      const blackBoxCovered = await this.getDataAdapter().getBlackBoxCovered(func._id);
      const whiteBoxCovered = await this.getDataAdapter().getWhiteBoxCovered(func._id);
      const manualCovered = await this.getDataAdapter().getManualCovered(func._id);
      const blackBoxTestIds = blackBoxCovered.map((covered) => covered._id.toString());
      const whiteBoxTestIds = whiteBoxCovered.map((covered) => covered.testcaseId.toString());
      const manualTestIds = manualCovered.map((covered) => covered._id.toString());
      if (this.willTestStatements()) {
        generateStatemetBlocks([func], linesInSourceFile, [blackBoxTestIds, whiteBoxTestIds, manualTestIds]);
      }
      if (this.willTestBranch()) {
        generateBranchBlocks([func], linesInSourceFile, [blackBoxTestIds, whiteBoxTestIds, manualTestIds]);
      }
      if (this.willTestCondition()) {
        generateConditionBlocks([func], [blackBoxTestIds, whiteBoxTestIds, manualTestIds]);
      }
      if (this.willTestDecision()) {
        generateDecisionBlocks([func], linesInSourceFile, [blackBoxTestIds, whiteBoxTestIds, manualTestIds]);
      }
      if (this.willTestMcdc()) {
        // 算法一
        if (this.version.mcdcKind === mcdcKind.masking) {
          generateMcDcBlocks([func], [blackBoxTestIds, whiteBoxTestIds, manualTestIds]);
          // 算法二
        } else {
          generateCondMcDcBlocks([func], [blackBoxTestIds, whiteBoxTestIds, manualTestIds]);
        }
      }
      func.manualCovered = manualCovered.map((covered) => {
        let beginLine = '';
        let endLine = '';
        if (func.functionCfg.nodes[covered.nodeId]) {
          beginLine = func.functionCfg.nodes[covered.nodeId].begin.line;
          endLine = func.functionCfg.nodes[covered.nodeId].end.line;
        }
        return {
          lines: beginLine === endLine ? beginLine : `${beginLine}-${endLine}`,
          description: covered.description,
        };
      });
      const groupCoverageInfo = {
        black: {},
        white: {},
        manual: {},
        total: {
          statementCovered: func.statementCovered,
          statementTotal: func.statementTotal,
          branchCovered: func.branchCovered,
          branchTotal: func.branchTotal,
          conditionCovered: func.conditionCovered,
          conditionTotal: func.conditionTotal,
          decisionCovered: func.decisionCovered,
          decisionTotal: func.decisionTotal,
          mcdcCovered: func.mcdcCovered,
          mcdcTotal: func.mcdcTotal,
        },
      };
      const funcPureCfg = await this.getDataAdapter().getFunctionCfg(func, false);
      [['black', blackBoxCovered], ['white', whiteBoxCovered], ['manual', manualCovered]].forEach(([key, covereds]) => {
        const funcPureCfgCopy = JSON.parse(JSON.stringify(funcPureCfg));
        covereds.forEach((covered) => {
          if (covered.coveredFields) {
            functions.mergeCfgCovered(funcPureCfgCopy, covered.coveredFields, covered.testcaseId || covered._id);
          }
        });
        if (this.version.mcdcKind === mcdcKind.masking) {
          functions.completeDecisionCases(funcPureCfgCopy, this.version.mcdcKind);
          functions.mergeDecisionCasesTestIds(funcPureCfgCopy);
        }
        const {
          statementCovered,
          statementTotal,
          branchCovered,
          branchTotal,
          conditionCovered,
          conditionTotal,
          decisionCovered,
          decisionTotal,
          mcdcCovered,
          mcdcTotal,
        } = functions.getCoverage(funcPureCfgCopy, this.version.mcdcKind);
        groupCoverageInfo[key].statementCovered = statementCovered;
        groupCoverageInfo[key].statementTotal = statementTotal;
        groupCoverageInfo[key].branchCovered = branchCovered;
        groupCoverageInfo[key].branchTotal = branchTotal;
        groupCoverageInfo[key].conditionCovered = conditionCovered;
        groupCoverageInfo[key].conditionTotal = conditionTotal;
        groupCoverageInfo[key].decisionTotal = decisionTotal;
        groupCoverageInfo[key].decisionCovered = decisionCovered;
        groupCoverageInfo[key].mcdcCovered = mcdcCovered;
        groupCoverageInfo[key].mcdcTotal = mcdcTotal;
      });
      func.coverageInfo = groupCoverageInfo;
    });
    funcs.map((func) => {
      func.codeCoverageDetails = [];
      if (this.willTestStatements()) {
        func.codeCoverageDetails.push(func.statementsBlocks);
      }
      if (this.willTestBranch() && func.branchTotal > 0) {
        func.codeCoverageDetails.push(func.branchBlocks);
      }
      if (this.willTestCondition() && func.conditionTotal > 0) {
        func.codeCoverageDetails.push(func.conditionsBlocks);
      }
      if (this.willTestDecision() && func.decisionTotal > 0) {
        func.codeCoverageDetails.push(func.decisionBlocks);
      }
      if (this.willTestMcdc() && func.mcdcTotal > 0) {
        func.codeCoverageDetails.push(func.mcdcBlocks || func.condMcdcBlocks);
      }
      return func;
    });
    await fs.ensureDir(path.dirname(reportPath));
    await this.generateFileReport(
      {
        isCodeInstrumentation: this.version.mode === 'codeInstrumentation',
        fileName: file.fileName,
        summary: DefaultReporter.getSummaryOfFile(funcs),
        functions: this.getFunctionDetailsOfFile(typeSystem, funcs),
        isIntegrationTest: this.isIntegrationTest(),
        reportName: this.getReportName(),
      },
      reportPath
    );
    return { reportPath };
  }

  getSummaryOfFunction(func) {
    const coverageArr = [];
    const { coverages } = this.version;
    ['statement', 'branch', 'condition', 'decision', 'mcdc'].forEach((kind) => {
      const summary = {};
      ['black', 'white', 'manual', 'total'].forEach((type) => {
        summary[type] = {};
        // eslint-disable-next-line no-bitwise
        if (func.coverageInfo[type][`${kind}Total`] && (coverages & coverageKind[kind]) > 0) {
          summary[type].value = func.coverageInfo[type][`${kind}Covered`] / func.coverageInfo[type][`${kind}Total`];
          summary[type].value = `${Math.round(summary[type].value * 10000) / 100}%`;
        } else if ((coverages & coverageKind[kind]) > 0) {
          summary[type].value = '-';
        }
      });
      if (summary.total.value) {
        summary.name = this.i18n.__(`tests.${kind}CoverageRate`);
        coverageArr.push(summary);
      }
    });
    return { coverages: coverageArr, codeCoverageDetails: func.codeCoverageDetails };
  }

  generateSummaryReport(data, output, config = {}) {
    return this.generateReport('summary-template', data, output, { isSummary: true, generateToc: true, ...config });
  }

  /**
   * 获取函数列表详细信息
   * @param {Object} typeSystem 类型系统
   * @param {Array} functions 函数列表
   * @returns {Array}
   */
  getFunctionDetailsOfFile(typeSystem, functions) {
    const resFunctions = [];
    functions.sort((a, b) => {
      if (a.index < b.index) return -1;
      if (a.index > b.index) return 1;
      return 0;
    });
    functions.forEach((func) => {
      resFunctions.push({
        functionName: func.functionName,
        description: func.description,
        udfs: func.udfs,
        codeDefects: this.getCodeDefects(func),
        // to be modified
        summary: this.getSummaryOfFunction(func),
        tests: this.getTestDetailsOfFunction(typeSystem, func),
        integrationFunctions: func.integrationFunctions,
        integrationFunctionsCount: func.integrationFunctions.length,
        manualCovered: func.manualCovered || [],
      });
    });
    return resFunctions;
  }

  static codeInstrumentationSupport() {
    return true;
  }
}

/**
 * Export model definition object.
 */
module.exports = DefaultReporter;
